﻿using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.UIElements;

public struct ShadowDirLight
{
    public int visiableLightIndex;
}

/// <summary>
/// 阴影渲染
/// </summary>
public class Shadowing
{
    const string bufferName = "Shadowing";
    CommandBuffer buffer = new CommandBuffer()
    {
        name = bufferName,
    };

    ScriptableRenderContext context;
    CullingResults cullingResults;
    ShadowSettings shadowSettings;

    /// <summary>
    /// 阴影方向光数量
    /// </summary>
    int shadowDirLightCount;
    /// <summary>
    /// 阴影方向光最大数量
    /// </summary>
    const int maxShadowDirLightCount = 4;

    // 阴影图集
    static int dirShadowAtlasId = Shader.PropertyToID("_DirectionalShadowAtlas");
    static int dirShadowMatricesId = Shader.PropertyToID("_DirectionalShadowMatrices");

    // 世界坐标转阴影贴图的像素坐标，变换矩阵
    static Matrix4x4[] dirShadowMatrices = new Matrix4x4[maxShadowDirLightCount];
    /// <summary>
    /// 阴影方向光
    /// </summary>
    ShadowDirLight[] shadowDirLights = new ShadowDirLight[maxShadowDirLightCount];

    public void Setup(ScriptableRenderContext contenxt, CullingResults cullingResults, ShadowSettings shadowSettings)
    {
        this.context = contenxt;
        this.cullingResults = cullingResults;
        this.shadowSettings = shadowSettings;
        shadowDirLightCount = 0;
    }

    public Vector3 ReserveDirShadows(Light light, int visiableLightIndex)
    {
        if (shadowDirLightCount < maxShadowDirLightCount &&
            light.shadows != LightShadows.None &&
            light.shadowStrength > 0 &&
            cullingResults.GetShadowCasterBounds(visiableLightIndex, out Bounds b))     // 若光源影响了场景中至少一个阴影投射对象，则为true
        {
            shadowDirLights[shadowDirLightCount] = new ShadowDirLight { visiableLightIndex = visiableLightIndex };
            return new Vector3(light.shadowStrength, shadowDirLightCount++);
        }
        return Vector3.zero;
    }

    public void Render()
    {
        if (shadowDirLightCount > 0)
        {
            RenderDirShadow();
        }
        else
        {
            buffer.GetTemporaryRT(dirShadowAtlasId, 1, 1, 32, FilterMode.Bilinear, RenderTextureFormat.Shadowmap);
        }
    }

    private void RenderDirShadow()
    {
        int atlasSize = (int)shadowSettings.directional.atlasSize;
        // 创建临时渲染纹理
        buffer.GetTemporaryRT(dirShadowAtlasId, atlasSize, atlasSize, 32, FilterMode.Bilinear, RenderTextureFormat.Shadowmap);
        // 渲染阴影贴图时，确保每次渲染都从空白状态开始，并将结果存储到阴影贴图中
        buffer.SetRenderTarget(dirShadowAtlasId, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store);
        buffer.ClearRenderTarget(true, false, Color.clear);

        ExecuteBuffer();

        for (int i = 0; i < shadowDirLightCount; i++)
        {
            RenderDirShadows(i, atlasSize);
        }
    }

    private void RenderDirShadows(int index, int tileSize)
    {
        ShadowDirLight light = shadowDirLights[index];
        ShadowDrawingSettings shadowSettings = new ShadowDrawingSettings(cullingResults, light.visiableLightIndex, BatchCullingProjectionType.Orthographic);
        cullingResults.ComputeDirectionalShadowMatricesAndCullingPrimitives(light.visiableLightIndex, 0, 1, Vector3.zero, tileSize, 0f, out Matrix4x4 viewMatrix, out Matrix4x4 projMatrix, out ShadowSplitData splitData);

        shadowSettings.splitData = splitData;
        buffer.SetViewProjectionMatrices(viewMatrix, projMatrix);

        ExecuteBuffer();

        // 只渲染包含 ShadowCaster Pass 的材质
        context.DrawShadows(ref shadowSettings);
    }


    /// <summary>
    /// 设置 tile 的渲染区域
    /// </summary>
    /// <param name="index">tile 索引</param>
    /// <param name="split">一行中 tile 的个数</param>
    /// <param name="tileSize">tile 宽/高 </param>
    void SetTileViewport(int index, int split, int tileSize)
    {
        Vector2 coord = new Vector2(index % split, index / split); // tile 的索引坐标，左下角为（0,0）
        buffer.SetViewport(new Rect(coord.x * tileSize, coord.y * tileSize, tileSize, tileSize));
    }

    /// <summary>
    /// 将灯光的世界矩阵转换为：从世界坐标转换为uv坐标（用于从深度图集中采样）的矩阵
    /// </summary>
    /// <param name="matrix"></param>
    /// <param name="split"></param>
    /// <param name="offset"></param>
    /// <returns></returns>
    Matrix4x4 ConvertToAtlasMatrix(Matrix4x4 matrix, int split, Vector2 offset)
    {
        if (SystemInfo.usesReversedZBuffer)
        {
            // 讲矩阵的第三行取反，因为矩阵转换时，为列矩阵左乘向量，第三行乘向量z坐标
            matrix.SetRow(2, -matrix.GetRow(2));
        }

        // 左侧乘以矩阵：
        // | 0.5  0    0    0.5 |
        // | 0    0.5  0    0.5 |
        // | 0    0    0.5  0.5 |
        // | 0    0    0    1   |

        // 用缩放、平移矩阵左乘 matrix，因为矩阵为列矩阵（左乘向量），所以在计算后再做缩放和平移，则需要缩放、平移矩阵放在左侧
        matrix.SetRow(0, 0.5f * (matrix.GetRow(0) + matrix.GetRow(3)));
        matrix.SetRow(1, 0.5f * (matrix.GetRow(1) + matrix.GetRow(3)));
        matrix.SetRow(2, 0.5f * (matrix.GetRow(2) + matrix.GetRow(3)));

        // 缩放和偏移xy坐标到对应tile
        // 左侧乘以矩阵：
        // | scale  0       0       offset.x * scale |
        // | 0      scale   0       offset.y * scale |
        // | 0      0       1       0                |
        // | 0      0       0       1                |

        // 第四列 xy 乘以 scale，是因为缩放是从坐标原点缩放的（而不是模型中心点），所以偏移也是 scale

        float scale = 1f / split;
        matrix.SetRow(0, scale * matrix.GetRow(0) + offset.x * scale * matrix.GetRow(3));
        matrix.SetRow(1, scale * matrix.GetRow(1) + offset.y * scale * matrix.GetRow(3));

        return matrix;
    }

    public void CleanUp()
    {
        // 手动释放 ShadowAtlas
        buffer.ReleaseTemporaryRT(dirShadowAtlasId);
        ExecuteBuffer();
    }

    void ExecuteBuffer()
    {
        context.ExecuteCommandBuffer(buffer);
        buffer.Clear();
    }
}
